package org.six11.ss.server;

import java.io.PrintWriter;
import java.util.*;

import org.jdom.Element;
import org.six11.ss.server.bean.*;
import org.six11.ss.shared.Message;
import org.six11.util.Debug;
import org.six11.util.adt.MultiState;
import org.six11.util.io.FileUtil;

import static org.six11.ss.shared.GameStatusConstants.STATUS_PLAY;
import static org.six11.ss.shared.GameStatusConstants.STATUS_NOTIFY_ROLES;

/**
 * Logic component for handling chat messages and routing them to the appropriate people.
 * 
 * @author Gabe Johnson <johnsogg@cmu.edu>
 */
public class ChatHandler extends AppletMessageHandler {

  private Comparator<Label> labelComparator;
  private Random random = new Random(System.currentTimeMillis());

  /**
   * @param state
   */
  public ChatHandler(MultiState state) {
    super(state);
    labelComparator = new Comparator<Label>() {
      public int compare(Label o1, Label o2) {
        Long t1 = Long.parseLong(o1.getTimeReceived());
        Long t2 = Long.parseLong(o2.getTimeReceived());
        return t1.compareTo(t2);
      }
    };
  }

  /**
   * 
   */
  @Override
  public void handle(Message m, String sessionID, PrintWriter out) {
    Person sender = getPersonBySession(sessionID);
    m.addParam("from", sender.getNick());
    boolean pt = isPlayTime(sender.getId() + "");
    logChat(sender.getRoom().getId() + "", sender.getId() + "", sender.getNick(), pt, m
        .getParam("content"));

    // during 'play' time, disable routing messages to everybody.
    if (pt) {
      addLabel(sender.getId() + "", sender.getRoom().getCurrentGame().getCurrentRound().getId()
          + "", m.getParam("content"));
    } else {
      // send message to everybody in the room
      Room room = sender.getRoom();
      RoomHandler rh = (RoomHandler) getHandler("room");
      rh.sendMessageToAll(m, room);
    }
  }

  /**
   * If there is a value in the state's CHAT_LOG_FILE slot, log the message.
   * 
   * @param roomID
   *          the room ID the player is in
   * @param playerID
   *          the player's ID
   * @param playerNick
   *          the player's nick
   * @param pt
   *          true if the chat is a label, false if it is just a message
   * @param msg
   *          the message text
   */
  private void logChat(String roomID, String playerID, String playerNick, boolean pt, String msg) {
    if (state.hasValue(ServerStateNames.CHAT_LOG_FILE)) {
      StringBuilder sb = new StringBuilder();
      sb.append(roomID + " " + (pt ? "L" : "C") + " " + Debug.now() + " " + playerID + " "
          + playerNick + ": " + msg + "\n");
      FileUtil.writeStringToFile(state.getString(ServerStateNames.CHAT_LOG_FILE), sb.toString(),
          true);
    }
  }

  private void addLabel(String personID, String roundID, String labelText) {
    try {
      Person labeler = loadPerson(personID);
      Round round = loadRound(roundID);
      Sketch sketch = round.getSketch();
      Label label = new Label(labelText, sketch, labeler, "" + System.currentTimeMillis());
      getSession().save(label);
      Message reply = new Message("labelOK");
      reply.addParam("text", labelText);
      getMessageQueue(labeler).add(reply);
      languageAnalyzer.addLabel(sketch.getId() + "", labelText);
      ScoreHandler sh = (ScoreHandler) getHandler("score");
      sh.awardPoints(labeler.getId() + "", random.nextInt(5));
      sh.awardPoints(sketch.getRound().getSketcher().getId() + "", random.nextInt(5));
    } catch (Exception ex) {
      bug("Couldn't add label '" + labelText + "' for person " + personID + " in round " + roundID);
      ex.printStackTrace();
    }
  }

  private boolean isPlayTime(String personID) {
    // determine if the person is in a game, and if that game's current round is in 'status play'.
    boolean ret = false;
    try {
      String currentStatus = loadPerson(personID).getRoom().getCurrentGame().getCurrentRound()
          .getStatus();
      ret = currentStatus.equals(STATUS_NOTIFY_ROLES) || currentStatus.equals(STATUS_PLAY);
    } catch (Exception ex) {
      // if something in the chain is null, we'll just return false.
    }
    return ret;
  }

  /**
   * Sends the given game's most recent labels to everybody in the room.
   * 
   * @param gameID
   */
  public void sendLabels(String gameID) {
    try {
      Message labelMessage = new Message("labels");
      Game game = loadGame(gameID);
      Round round = game.getCurrentRound();
      Sketch sketch = round.getSketch();
      Set<Label> labels = sketch.getLabels();
      labelMessage.addParam("game", game.getId() + "");
      labelMessage.addParam("round", round.getId() + "");
      SortedSet<Label> sortedLabels = new TreeSet<Label>(labelComparator);
      sortedLabels.addAll(labels);
      for (Label label : sortedLabels) {
        Element elm = new Element("label");
        elm.setAttribute("labeler", label.getLabeler().getNick());
        elm.setAttribute("text", label.getText());
        elm.setAttribute("serverTime", label.getTimeReceived());
        labelMessage.addElement(elm);
      }
      RoomHandler rh = (RoomHandler) getHandler("room");
      rh.sendMessageToAll(labelMessage.copy(), game.getRoom());
      bug("Sent " + labels.size() + " labels to everybody in the room.");
    } catch (Exception ex) {
      log("Unable to sendLabels for game " + gameID);
      ex.printStackTrace();
    }
  }
}
